home *** CD-ROM | disk | FTP | other *** search
/ MacAddict 114 / macaddict114.cdr / Software / Utilities / macam.0.8.4.dmg / macam sources / component_specific / MyBridge.m < prev    next >
Encoding:
Text File  |  2005-08-15  |  17.9 KB  |  642 lines

  1. /*
  2.     macam - webcam app and QuickTime driver component
  3.     Copyright (C) 2002 Matthias Krauss (macam@matthias-krauss.de)
  4.  
  5.     This program is free software; you can redistribute it and/or modify
  6.     it under the terms of the GNU General Public License as published by
  7.     the Free Software Foundation; either version 2 of the License, or
  8.     (at your option) any later version.
  9.  
  10.     This program is distributed in the hope that it will be useful,
  11.     but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13.     GNU General Public License for more details.
  14.  
  15.     You should have received a copy of the GNU General Public License
  16.     along with this program; if not, write to the Free Software
  17.     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  18.  $Id: MyBridge.m,v 1.4 2005/08/15 05:28:25 hxr Exp $
  19. */
  20.  
  21.  
  22. #import "MyBridge.h"
  23. #include "MiscTools.h"
  24. #import "MyCameraDriver.h"
  25. #import "MyCameraCentral.h"
  26. #import "RGB888Scaler.h"
  27.  
  28. @interface MyBridge (Private)
  29.  
  30. - (BOOL) privUpdateFormat;        //check format and update it (including grab world) if neeeded 
  31. - (BOOL) privSetImageBuffer;        //update all if neccessary, ensure grab running, set image buffer to our temp buffer
  32.  
  33. @end
  34.  
  35. @implementation MyBridge
  36.  
  37. - (id) initWithCentral:(MyCameraCentral*)in_central cid:(unsigned long)in_cid {
  38.     BOOL ok=YES;
  39.     self=[super init];
  40. //Set private attributes
  41.     central=in_central;
  42.     cid=in_cid;
  43.     driver=NULL;
  44.     scaler=NULL;
  45.     
  46.     clientBufferIdx=0;
  47.     driverBufferIdx=0;
  48.     clientImagePending=NO;
  49.  
  50.     driverStarted=NO;
  51.     driverShuttingDown=NO;
  52.     driverGrabRunning=NO;
  53.     driverFormatChangePending=NO;
  54.  
  55.     stateLock=NULL;
  56.     formatChangeLock=NULL;
  57.     shutdownLock=NULL;
  58.  
  59.     wantedResolution=ResolutionSIF;        //Just to have a default
  60.     wantedFps=5;                //Just to have a default
  61.     wantedCompression=0;            //Just to have a default
  62.  
  63.     memset(grabBuffers,0,NUM_BRIDGE_GRAB_BUFFERS*sizeof(BridgeGrabBuffer));    //Clear grab buffer array
  64.     
  65. //Start things up
  66.     if (!central) ok=NO;
  67.     if (ok) [central retain];
  68.     if (ok) {
  69.         formatChangeLock=[[NSLock alloc] init];
  70.         if (!formatChangeLock) ok=NO;
  71.     }
  72.     if (ok) {
  73.         stateLock=[[NSLock alloc] init];
  74.         if (!stateLock) ok=NO;
  75.     }
  76.     if (ok) {
  77.         shutdownLock=[[NSLock alloc] init];
  78.         if (!shutdownLock) ok=NO;
  79.         else [shutdownLock lock];        //locked by default
  80.     }
  81.     if (ok) {
  82.         scaler=[[RGB888Scaler alloc] init];
  83.         if (!scaler) ok=NO;
  84.     }
  85.     if (!ok) {
  86.         if (stateLock) { [stateLock release]; stateLock=NULL; }
  87.         if (formatChangeLock) { [formatChangeLock release]; formatChangeLock=NULL; }
  88.         if (central) { [central release]; central=NULL; }
  89.         if (central) { [central release]; central=NULL; }
  90.         if (scaler) { [scaler release]; scaler=NULL; }
  91.         return NULL;
  92.     }
  93.     return self;
  94. }
  95.  
  96. - (unsigned long) cid {
  97.     return cid;
  98. }
  99.  
  100. - (void) dealloc {
  101.     short i;
  102.     [stateLock lock];
  103.     NSAssert(!driverFormatChangePending,@"Bridge trying to dealloc with a format change still pending. This is going to be fun...");
  104.     if (formatChangeLock) { [formatChangeLock release]; formatChangeLock=NULL; }
  105.     if (shutdownLock) { [shutdownLock release]; shutdownLock=NULL; }
  106.     if (central) { [central release]; central=NULL; }
  107.     if (scaler) { [scaler release]; scaler=NULL; }
  108.     [stateLock unlock];
  109.     if (stateLock) { [stateLock release]; stateLock=NULL; }
  110.     for (i=0;i<NUM_BRIDGE_GRAB_BUFFERS;i++) {
  111.         if (grabBuffers[i].data) FREE(grabBuffers[i].data,"MyBridge dealloc grab buffer");
  112.         grabBuffers[i].resolution=0;
  113.         grabBuffers[i].data=NULL;
  114.     }
  115.     [super dealloc];
  116. }
  117.  
  118. - (BOOL) startup {
  119.     NSAutoreleasePool* pool=[[NSAutoreleasePool alloc] init];
  120.     BOOL ok=YES;
  121.     NSAssert(!driver,@"Bridge: There's already a driver on startup!");
  122.     [stateLock lock];                //mutex
  123.     if (driverStarted) ok=NO;
  124.     if (ok) {
  125.         driverShuttingDown=NO;
  126.         [central setDelegate:self];            //The delegate is propagated to new cameras
  127.         [central useCameraWithID:cid to:&driver acceptDummy:YES];
  128.         if (!driver) [central useDummyForError:CameraErrorNoCam];
  129.         [central setDelegate:NULL];
  130.         if (driver) [driver retain];
  131.         else ok=NO;
  132.     }
  133.     if (ok) {
  134.         wantedResolution=[driver resolution];
  135.         wantedFps=[driver fps];
  136.         wantedCompression=[driver compression];        
  137.         ok=[self privUpdateFormat];
  138.     }
  139.     if (ok) {
  140.         ok=[scaler setDestinationWidth:[driver width] height:[driver height]];    //reset scaling
  141.     }
  142.     if (ok) {
  143.         clientBufferIdx=0;
  144.         driverBufferIdx=0;
  145.         clientImagePending=NO;
  146.         driverStarted=YES;
  147.     }
  148.     [stateLock unlock];                //mutex
  149.     [pool release];
  150.     return ok;
  151. }
  152.  
  153. - (void) shutdown {
  154.     NSAutoreleasePool* pool=[[NSAutoreleasePool alloc] init];
  155.     if (!driverStarted) return;
  156.     if (driverShuttingDown) return;
  157.     driverShuttingDown=YES;
  158.     [driver shutdown];
  159.     [shutdownLock lock];            //Wait until driver has been shut down
  160.     [stateLock lock];                //mutex
  161.     driverStarted=NO;
  162.     driverShuttingDown=NO;
  163.     [stateLock unlock];                //mutex
  164.     [pool release];
  165. }
  166.  
  167.  
  168. //-----------------
  169. //   Doing grabs
  170. //-----------------
  171.  
  172. - (BOOL) grabOneFrameCompressedAsync {
  173.     [stateLock lock];                //mutex
  174.     if (!clientImagePending) {
  175.         clientImagePending=YES;
  176.         if (!driverGrabRunning) {
  177.             if (![self privSetImageBuffer]) clientImagePending=NO;
  178.         }
  179.     }
  180.     [stateLock unlock];                //mutex
  181.     return clientImagePending;
  182. }
  183.  
  184. - (BOOL) compressionDoneTo:(unsigned char **)data        //Returns if grabOneFrameCompressedAsync has finished
  185.                       size:(long*)size
  186.                 similarity:(UInt8*)similarity {
  187.     BOOL ok=NO;
  188.     if (clientImagePending) return NO;        //fast skip without lock
  189.     [stateLock lock];                //mutex
  190.     if (!clientImagePending) {
  191.         if (data) {
  192.             *data=[scaler convertSourceData:grabBuffers[clientBufferIdx].data
  193.                                       width:WidthOfResolution(grabBuffers[clientBufferIdx].resolution)
  194.                                      height:HeightOfResolution(grabBuffers[clientBufferIdx].resolution)];
  195.             if (*data) {
  196.                 if (size) *size=[scaler destinationDataSize];
  197.                 if (similarity) *similarity=0;
  198.                 ok=YES;
  199.             }
  200.         }
  201.     }
  202.     [stateLock unlock];                //mutex
  203.     return ok;
  204. }
  205.  
  206. - (void) takeBackCompressionBuffer:(Ptr)buf {
  207.     // Something to be done here? Let's hope no checking is necessary... ***
  208. }
  209.  
  210. - (BOOL) setDestinationWidth:(long) width height:(long)height {
  211.     return [scaler setDestinationWidth:width height:height];
  212. }
  213.  
  214. - (BOOL) getAnImageDescriptionCopy:(ImageDescriptionHandle)outHandle {
  215.     ImageDescription* desc;
  216.     long size=sizeof(ImageDescription);
  217.     BOOL ok=YES;
  218.     [stateLock lock];                //mutex
  219.     SetHandleSize((Handle)outHandle,size);
  220.     if (GetHandleSize((Handle)outHandle)!=size) ok=NO;
  221.     if (ok) {
  222.         HLock((Handle)outHandle);
  223.         desc=(ImageDescription*)(*outHandle);
  224.         desc->idSize=size;
  225.         desc->cType=kRawCodecType;
  226.         desc->resvd1=0;
  227.         desc->resvd2=0;
  228.         desc->dataRefIndex=0;
  229.         desc->version=1;
  230.         desc->revisionLevel=1;
  231.         desc->vendor='APPL';
  232.         desc->temporalQuality=codecLosslessQuality;
  233.         desc->spatialQuality=codecLosslessQuality;
  234.         desc->width=[scaler destinationWidth];
  235.         desc->height=[scaler destinationHeight];
  236.         desc->hRes=Long2Fix(36);
  237.         desc->vRes=Long2Fix(36);
  238.         desc->dataSize=0;
  239.         desc->frameCount=1;
  240.         CStr2PStr("Raw RGB data",desc->name);
  241.         desc->depth=24;
  242.         desc->clutID=-1;
  243.         HUnlock((Handle)outHandle);
  244.     }
  245.     [stateLock unlock];                //mutex
  246.     return ok;
  247. }
  248.  
  249. - (BOOL) isStarted {
  250.     BOOL ret;
  251.     [stateLock lock];                //mutex - prabably not necessary
  252.     ret=driverStarted;
  253.     [stateLock unlock];                //mutex
  254.     return ret;
  255. }
  256.  
  257. - (BOOL) isCameraValid {
  258.     BOOL valid=YES;
  259.     [stateLock lock];                //mutex
  260.     if (!driver) valid=NO;
  261.     else if (![driver realCamera]) valid=NO;
  262.     [stateLock unlock];                //mutex
  263.     return valid;
  264. }
  265.  
  266. - (BOOL) getName:(char*)name {
  267.     if (!driverStarted) return NO;
  268.     return [central getName:name forID:cid];
  269. }
  270.  
  271. - (BOOL)canSetContrast {
  272.     if (driver) return [driver canSetContrast];
  273.     else return NO;
  274. }
  275.  
  276. - (unsigned short)contrast {
  277.     if (driver) return (unsigned short)([driver contrast]*65535.0f);
  278.     else return 0;
  279. }
  280.  
  281. - (void)setContrast:(unsigned short)c {
  282.     if (driver) [driver setContrast:((float)c)/65535.0f];
  283. }
  284.  
  285. - (BOOL)canSetBrightness {
  286.     if (driver) return [driver canSetBrightness];
  287.     else return NO;
  288. }
  289.  
  290. - (unsigned short)brightness {
  291.     if (driver) return (unsigned short)([driver brightness]*65535.0f);
  292.     else return 0;
  293. }
  294.  
  295. - (void)setBrightness:(unsigned short)c {
  296.     if (driver) [driver setBrightness:((float)c)/65535.0f];
  297. }
  298.  
  299. - (BOOL)canSetSaturation {
  300.     if (driver) return [driver canSetSaturation];
  301.     else return NO;
  302. }
  303.  
  304. - (unsigned short)saturation {
  305.     if (driver) return (unsigned short)([driver saturation]*65535.0f);
  306.     else return 0;
  307. }
  308.  
  309. - (void)setSaturation:(unsigned short)c {
  310.     if (driver) [driver setSaturation:((float)c)/65535.0f];
  311. }
  312.  
  313. - (BOOL)canSetSharpness {
  314.     if (driver) return [driver canSetSharpness];
  315.     else return NO;
  316. }
  317.  
  318. - (unsigned short)sharpness {
  319.     if (driver) return (unsigned short)([driver sharpness]*65535.0f);
  320.     else return 0;
  321. }
  322.  
  323. - (void)setSharpness:(unsigned short)c {
  324.     if (driver) [driver setSharpness:((float)c)/65535.0f];
  325. }
  326.  
  327. - (BOOL)canSetGamma {
  328.     if (driver) return [driver canSetGamma];
  329.     else return NO;
  330. }
  331.  
  332. - (unsigned short)gamma {
  333.     if (driver) return (unsigned short)([driver gamma]*65535.0f);
  334.     else return 0;
  335. }
  336.  
  337. - (void)setGamma:(unsigned short)c {
  338.     if (driver) [driver setGamma:((float)c)/65535.0f];
  339. }
  340.  
  341. - (BOOL)canSetHFlip {
  342.     if (driver) return [driver canSetHFlip];
  343.     else return NO;
  344. }
  345.  
  346. - (BOOL)hFlip {
  347.     if (driver) return [driver hFlip];
  348.     else return NO;
  349. }
  350.  
  351. - (void)setHFlip:(BOOL)c {
  352.     if (driver) [driver setHFlip:c];
  353. }
  354.  
  355. - (BOOL)canSetGain {
  356.     if (driver) return [driver canSetGain];
  357.     else return NO;
  358. }
  359.  
  360. - (void)setGain:(unsigned short)v {
  361.     if (driver) [driver setGain:((float)v)/65535.0f];
  362. }
  363.  
  364. - (unsigned short)gain {
  365.     if (driver) return (unsigned short)([driver gain]*65535.0f);
  366.     else return NO;
  367. }
  368.  
  369. - (BOOL)canSetShutter {
  370.     if (driver) return [driver canSetShutter];
  371.     else return NO;
  372. }
  373.  
  374. - (void)setShutter:(unsigned short)v {
  375.     if (driver) [driver setShutter:((float)v)/65535.0f];
  376. }
  377.  
  378. - (unsigned short)shutter {
  379.     if (driver) return (unsigned short)([driver shutter]*65535.0f);
  380.     else return 0;
  381. }
  382.  
  383. - (BOOL)canSetAutoGain {
  384.     if (driver) return [driver canSetAutoGain];
  385.     else return NO;
  386. }
  387.  
  388. - (void)setAutoGain:(BOOL)v {
  389.     if (driver) [driver setAutoGain:v];
  390. }
  391.  
  392. - (BOOL)isAutoGain {
  393.     if (driver) return [driver isAutoGain];
  394.     else return NO;
  395. }
  396.  
  397. - (short) maxCompression {
  398.     if (driver) return [driver maxCompression];
  399.     else return 0;
  400. }
  401.  
  402. - (short) compression {
  403.     if (driver) return [driver compression];
  404.     else return 0;
  405. }
  406.  
  407. - (void) setCompression:(short)v {
  408.     [stateLock lock];
  409.     wantedCompression=v;
  410.     if ([self privUpdateFormat]) {        //change could be done right now
  411.         [stateLock unlock];
  412.     } else {                    //Was deferred
  413.         driverFormatChangePending=YES;
  414.         [formatChangeLock tryLock];        //Make sure it's locked
  415.         [stateLock unlock];
  416.         [formatChangeLock lock];        //Block until lock is released
  417.     }
  418. }
  419.  
  420. - (BOOL) canSetWhiteBalanceMode {
  421.     if (driver) return [driver canSetWhiteBalanceMode];
  422.     else return NO;
  423. }
  424.  
  425. - (BOOL) canSetWhiteBalanceModeTo:(WhiteBalanceMode)m {
  426.     if (driver) return [driver canSetWhiteBalanceModeTo:m];
  427.     else return (m==WhiteBalanceLinear);
  428. }
  429.  
  430. - (WhiteBalanceMode) whiteBalanceMode {
  431.     if (driver) return [driver whiteBalanceMode];
  432.     else return WhiteBalanceLinear;
  433. }
  434.  
  435. - (void) setWhiteBalanceMode:(WhiteBalanceMode)m {
  436.     if (driver) [driver setWhiteBalanceMode:m];
  437. }
  438.  
  439.  
  440. // ================= Color & Grey Mode
  441.  
  442. - (BOOL) canBlackWhiteMode {
  443.     if (driver) return [driver canBlackWhiteMode];
  444.     else return NO;
  445. }
  446.  
  447.  
  448. - (BOOL) blackWhiteMode {
  449.     if (driver)
  450.         return [driver blackWhiteMode];
  451.     else
  452.         return NO; // default to color mode
  453. }
  454.  
  455. - (void) setBlackWhiteMode:(BOOL)m {
  456.     if (driver) [driver setBlackWhiteMode:m];
  457. }
  458.  
  459. // =================== LED state
  460.  
  461. - (BOOL) canSetLed {
  462.     if (driver) return [driver canSetLed];
  463.     else return NO;
  464. }
  465.  
  466.  
  467. - (BOOL) isLedOn {
  468.     if (driver) return [driver isLedOn];
  469.     else return FALSE;
  470. }
  471.  
  472. - (void) setLed:(BOOL)v {
  473.     if (driver) [driver setLed:v];
  474. }
  475.  
  476. // =============================
  477.  
  478. - (short) width {
  479.     if (driver) return [driver width];
  480.     else return 1;
  481. }
  482.  
  483. - (short) height {
  484.     if (driver) return [driver height];
  485.     else return 1;
  486. }
  487.  
  488. - (void) nativeBounds:(Rect*)r { 
  489. //Note that when called un-state-locked, this might be inconsistent. But the client model is serial (and it doesn't matter)
  490.     if (r) {
  491.         r->left=0;
  492.         r->top=0;
  493.         r->right=[self width];
  494.         r->bottom=[self height];
  495.     }
  496. }
  497.  
  498. - (short) fps {
  499.     if (driver) return [driver fps];
  500.     else return 5;
  501. }
  502.  
  503. - (CameraResolution) resolution {
  504.     if (driver) return [driver resolution];
  505.     else return ResolutionSQSIF;
  506. }
  507.  
  508. - (BOOL) supportsResolution:(CameraResolution)res fps:(short)fps {
  509.     if (driver) return [driver supportsResolution:res fps:fps];
  510.     else return ((res==ResolutionSIF)&&(fps==5));
  511. }
  512.  
  513. - (void) setResolution:(CameraResolution)res fps:(short)fps {
  514.     [stateLock lock];
  515.     wantedResolution=res;
  516.     wantedFps=fps;
  517.     if ([self privUpdateFormat]) {        //could be done right now
  518.         [stateLock unlock];
  519.     } else {                    //Was deferred
  520.         driverFormatChangePending=YES;
  521.         [formatChangeLock tryLock];        //Make sure it's locked
  522.         [stateLock unlock];
  523.         [formatChangeLock lock];        //Block until format change is done
  524.     }
  525. }
  526.  
  527. - (void) imageReady:(id)cam {
  528. //This call comes from decodingThread, except if we've called makeErrorImage
  529.     [stateLock lock];                 //mutex
  530.     if (driver==cam) {
  531.         if ([driver imageBuffer]) {
  532.             if (clientImagePending) {
  533.                 clientBufferIdx=driverBufferIdx;
  534.                 clientImagePending=NO;
  535.                 driverBufferIdx=(driverBufferIdx+1)%NUM_BRIDGE_GRAB_BUFFERS;
  536.             }
  537.         }
  538.         [self privSetImageBuffer];
  539.     }
  540.     [stateLock unlock];                //mutex
  541. }
  542.  
  543. - (void) cameraHasShutDown:(id)cam {
  544.     [stateLock lock];                //mutex
  545.     if (driver==cam) {
  546.         [driver release];
  547.         driver=NULL;
  548.         if (driverShuttingDown) {
  549.             [shutdownLock unlock];
  550.         } else {
  551.             [central setDelegate:self];
  552.             driver=[central useDummyForError:CameraErrorNoCam];    //There's no callback
  553.             NSAssert(driver,@"MyBridge: cameraHasShutDown: Could not allocate dummy camera driver");
  554.             [central setDelegate:NULL];
  555.             [driver retain];
  556.             [self privSetImageBuffer];
  557.         }
  558.         if (driverFormatChangePending) {        //Hopefully there's no such situation
  559.             driverFormatChangePending=NO;
  560.             [formatChangeLock unlock];
  561.         }
  562.     }
  563.     [stateLock unlock];                //mutex
  564. }
  565.  
  566. - (void) grabFinished:(id)cam withError:(CameraError)err {
  567.     BOOL doErrorImage=NO;
  568.     [stateLock lock];                //mutex
  569.     if (driver==cam) {
  570.         [driver setImageBuffer:NULL bpp:3 rowBytes:1];    //Make sure the driver doesn't render any more
  571.         driverGrabRunning=NO;
  572.         [self privUpdateFormat];        //Make sure the format is up to date
  573.         if (clientImagePending) {
  574.             if (err) doErrorImage=YES;
  575.             else [self privSetImageBuffer];
  576.         }
  577.     }
  578.     [stateLock unlock];                //mutex
  579.     if (doErrorImage) [cam makeErrorImage:err];
  580. }
  581.  
  582. - (void) saveAsDefaults {
  583.     if (driver) [central saveCameraSettingsAsDefaults:driver];
  584. }
  585.  
  586. //------------------------------------
  587. //   Private method implementations
  588. //------------------------------------
  589.  
  590. //Private methods are not mutexed since they are all called internally - they have to be encapsulated by a stateLock
  591.  
  592. - (BOOL) privUpdateFormat {
  593.     BOOL ok=YES;
  594.     if (([self resolution]==wantedResolution)&&
  595.         ([self fps]==wantedFps)&&
  596.         ([self compression]==wantedCompression)) return YES;    //It's alright
  597.  
  598.     driverGrabRunning=[driver stopGrabbing];            //We need to stop
  599.     if (driverGrabRunning) return NO;                //Could not stop for now - we'll get called on finish
  600.     [driver setResolution:wantedResolution fps:wantedFps];    //Set resolution and fps
  601.     [driver setCompression:wantedCompression];            //Set compression
  602.     wantedResolution=[driver resolution];            //set wanted to current (it's all we can do for now)
  603.     wantedFps=[driver fps];                    //set wanted to current (it's all we can do for now)
  604.     wantedCompression=[driver compression];            //set wanted to current (it's all we can do for now)
  605.     if (driverFormatChangePending) {                //In case the a call is locked:
  606.         driverFormatChangePending=NO;                //Now not any more
  607.         [formatChangeLock unlock];                //The call may finish now
  608.     }
  609.     return ok;
  610. }
  611.  
  612. - (BOOL) privSetImageBuffer {
  613.     long bufferSize;
  614.     CameraResolution dRes;
  615.     
  616. //Make sure the format is ok
  617.     if (!driverGrabRunning) {
  618.         if (![self privUpdateFormat]) return NO;
  619.     }
  620.  
  621. //Check the buffer
  622.     dRes=[driver resolution];
  623.     if (dRes!=grabBuffers[driverBufferIdx].resolution) {
  624.         if (grabBuffers[driverBufferIdx].data) FREE(grabBuffers[driverBufferIdx].data,"privSetImageBuffer");
  625.         grabBuffers[driverBufferIdx].data=NULL;
  626.     }
  627. //If needed, allocate a new one
  628.     if (!(grabBuffers[driverBufferIdx].data)) {
  629.         grabBuffers[driverBufferIdx].resolution=dRes;
  630.         bufferSize=WidthOfResolution(dRes)*HeightOfResolution(dRes)*3;
  631.         MALLOC(grabBuffers[driverBufferIdx].data,unsigned char*,bufferSize,@"privSetImageBuffer");
  632.     }
  633. //Set image buffer
  634.     if (!(grabBuffers[driverBufferIdx].data)) return NO;
  635.     [driver setImageBuffer:grabBuffers[driverBufferIdx].data bpp:3 rowBytes:WidthOfResolution(dRes)*3];
  636.     if (!driverGrabRunning) driverGrabRunning=[driver startGrabbing];
  637.     return driverGrabRunning;
  638. }
  639.  
  640.  
  641. @end
  642.